#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include "util.hpp"
#include <mutex>
#include "log.hpp"

using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::unordered_map;
using std::vector;
namespace ns_index
{
    typedef struct DocInfo
    {
        string title;
        string content;
        string url;
        int doc_id; // 正排索引，数组下标就是doc_id
    } DocInfo;

    struct InvertedElem // 倒排结构体
    {
        int doc_id;
        string word; // 关键字
        int weight;  // 权重
    };

    struct InvertedElemPrint // 为了去重
    {
        int doc_id;
        vector<string> words;
        int weight;
    };

    typedef vector<InvertedElem> InvertedList; // 倒排拉链，一个关键字可能映射一至多个文档
    class Index
    {
        // 单例模式
    private:
        Index() {}
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;
        static Index *instance;
        static std::mutex mutex_; // 避免线程安全问题，加锁保护

    private:
        // 保存正排索引的数组
        vector<DocInfo> forward_index;
        // 保存关键字对倒排拉链（倒排结构体数组）的映射
        unordered_map<string, InvertedList> inverted_index;

    public:
        ~Index() {}
        static Index *GetInstance()
        {
            if (nullptr == instance)
            {
                mutex_.lock();
                if (nullptr == instance)
                    instance = new Index();
                mutex_.unlock();
            }
            return instance;
        }

    public:
        // 根据文档id在正排索引数组中找到对应的文档内容
        DocInfo *GetForwardIndex(int doc_id)
        {
            if (doc_id >= forward_index.size())
            {
                LOG(WARNING, "when GetForwardIndex doc_id out of range");
                // cerr << "when GetForwardIndex doc_id out of range" << endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        // 根据关键字在倒排索引哈希表找到倒排拉链
        InvertedList *GetInvertedList(const string &word)
        {
            auto iter = inverted_index.find(word);
            if (iter == inverted_index.end())
            {
                LOG(WARNING, "when GetInvertedList ,can't find key: " + word);
                // cerr << "when GetInvertedList ,can't find key: " << word
                //  << "in inverted_index" << endl;
                return nullptr;
            }
            return &iter->second;
        }

        bool BuildIndex(const string &input) // 根据parse模块生成的raw.txt来建立索引
        {
            // 逐行读取数据，分别建立正排索引和倒排索引
            std::ifstream in(input, std::ios_base::in);
            if (!in.is_open())
            {
                LOG(FATAL, "when BuildIndex,open file error: " + input);
                // cerr << "when BuildIndex,open file:" << input << " error" << endl;
                return false;
            }

            string inbuff;
            int num = 1;
            while (std::getline(in, inbuff))
            {
                if (num % 100 == 0)
                    LOG(NORMAL, "正在建立索引" + std::to_string(num));
                // cout << "正在建立第" << num << "个索引" << endl;
                ++num;
                DocInfo *doc = BuildForwardIndex(inbuff);
                if (nullptr == doc)
                {
                    LOG(DEBUG, "when BuildForwardIndex error,continue...");
                    // cerr << "when BuildForwardIndex error" << endl;
                    continue; // 建立正排索引失败，跳过当前文档
                }

                BuildInvertedIndex(*doc);
            }
            return true;
        }

        DocInfo *BuildForwardIndex(string &inbuff)
        {
            // 按\3切分字符串inbuff，构建DocInfo
            vector<string> strV;
            string SEP = "\3";
            ns_util::StringUtil::CutString(inbuff, strV, SEP);

            // 判断是否切分成三个字符串 title content url
            if (strV.size() != 3)
                return nullptr;

            // 构建DocInfo插入正排索引数组forward_index
            DocInfo doc;
            doc.title = strV[0];
            doc.content = strV[1];
            doc.url = strV[2];
            doc.doc_id = forward_index.size();
            forward_index.push_back(std::move(doc)); // 提高效率
            return &forward_index.back();
        }

        bool BuildInvertedIndex(DocInfo &doc)
        {
            // DocInfo{}建立word->倒排拉链映射
            struct word_cnt // 统计词频的结构体
            {
                size_t title_cnt = 0;   // 某个关键字在标题中的数量
                size_t content_cnt = 0; // 某个关键字在文本内容中的数量
            };
            vector<string> title_words, content_words;
            unordered_map<string, word_cnt> word_map;
            ns_util::JiebaUtil::CutString(doc.title, title_words);
            ns_util::JiebaUtil::CutString(doc.content, content_words);
            for (auto &word : title_words)
            {
                boost::to_lower(word); // 因为搜索时忽略大小写，统一将关键字转化为小写
                word_map[word].title_cnt++;
            }
            for (auto &word : content_words)
            {
                boost::to_lower(word); // 因为搜索时忽略大小写，统一将关键字转化为小写
                word_map[word].content_cnt++;
            }
            // 将该文本整理的关键字创建倒排结构体，插入对应的关键字映射的倒排拉链中
            for (auto &p : word_map)
            {
#define TITLE_WEIGHT 10
#define CONTENT_WEIGHT 1
                InvertedElem e;
                e.doc_id = doc.doc_id;
                e.word = p.first;
                e.weight = TITLE_WEIGHT * p.second.title_cnt + CONTENT_WEIGHT * p.second.content_cnt;
                inverted_index[p.first].push_back(std::move(e)); // 插入对应的关键字映射的倒排拉链中
            }
            return true;
        }
    };
    Index *ns_index::Index::instance = nullptr;
    std::mutex ns_index::Index::mutex_;
}
